// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using SnapObjects.Commons;

namespace SnapObjects.Data
{
    internal class DataIncludeGetter<TModel> : IDataIncludeGetter<TModel>
    {
        private readonly string _key;
        private readonly bool _isList;
        private readonly TModel _model;
        private readonly IEnumerable<TModel> _models;
        private readonly IModelAccessor<TModel> _accessor;
        private readonly IDataUnpacker _dataUnpacker;
        private readonly IDataPacker _dataPacker;

        public DataIncludeGetter(
            string key,
            TModel model,
            IDataUnpacker dataUnpacker)
            : this(key, false)
        {
            _model = model;
            _dataUnpacker = dataUnpacker;
        }

        public DataIncludeGetter(
            string key,
            IEnumerable<TModel> models,
            IDataUnpacker dataUnpacker)
            : this(key, true)
        {
            _models = models;
            _dataUnpacker = dataUnpacker;
        }

        public DataIncludeGetter(
            string key,
            TModel model,
            IDataPacker dataPacker)
            : this(key, false)
        {
            _model = model;
            _dataPacker = dataPacker;
        }

        public DataIncludeGetter(
            string key,
            IEnumerable<TModel> models,
            IDataPacker dataPacker)
            : this(key, true)
        {
            _models = models;
            _dataPacker = dataPacker;
        }

        private DataIncludeGetter(
            string key,
            bool isList)
        {
            _key = key;
            _isList = isList;

            var factory = ModelFactoryFactory.Create<TModel>();
            _accessor = factory.GetAccessor();
        }

        /// <summary>
        ///     Gets the data of an embedded property of the TModel object from the IDataUnpacker object.
        /// </summary>
        /// <typeparam name="TKey">The type of the embedded property in the TModel object to be included.</typeparam>
        /// <param name="key">The name specified for the data.</param>
        /// <param name="property">An expression used to specify the embedded property in the TModel object to be included.</param>
        /// <returns>Returns the current IDataIncludeGetter object.</returns>
        public IDataIncludeGetter<TModel> Include<TKey>(
            string key, Expression<Func<TModel, TKey>> property)
        {
            var propertyInfo = property.ExtractProperty();
            var formater = DataFormaterFactory.GetKeyFormater(this.GetDataFormat());

            void SetValue(string name, TModel model)
            {
                var raw = this.GetRaw(name);

                if (!String.IsNullOrWhiteSpace(raw))
                {
                    var propertyName = propertyInfo.Name;

                    var parser = DataParserFactory
                        .GetUnpackerParser<TKey>(this.GetDataFormat());

                    var value = parser.Parse(raw);

                    _accessor.SetValue(model, propertyName, value);
                }
            }

            if (_isList)
            {
                var n = 0;

                foreach (var model in _models)
                {
                    var name = new DataKey(
                        new DataKey(_key, n++),
                        new DataKey(key));

                    SetValue(formater.Format(name), model);
                }
            }
            else
            {
                var name = formater.Format(new DataKey(_key, key));

                SetValue(name, _model);
            }

            return this;
        }

        /// <summary>
        ///     Gets the data of all embedded properties of the TModel object from the IDataUnpacker object.
        /// </summary>
        /// <returns>Returns the current IDataIncludeGetter object.</returns>
        public IDataIncludeGetter<TModel> IncludeAll()
        {
            var embeddeds = ((IPocoModelMeta)_accessor.ModelMeta).EmbeddedProperties;
            var formater = DataFormaterFactory.GetKeyFormater(this.GetDataFormat());

            foreach (var embedded in embeddeds)
            {
                void SetValue(string key, TModel model)
                {
                    var raw = this.GetRaw(key);

                    if (!String.IsNullOrWhiteSpace(raw))
                    {
                        var propertyName = embedded.Name;

                        var parser = DataParserFactory
                            .GetUnpackerParser(embedded.DataType, this.GetDataFormat());

                        var value = parser.Parse(raw);

                        _accessor.SetValue(model, propertyName, value);
                    }
                }

                if (_isList)
                {
                    var n = 0;

                    foreach (var model in _models)
                    {
                        var name = new DataKey(
                            new DataKey(_key, n++),
                            new DataKey(embedded.Name));

                        SetValue(formater.Format(name), model);
                    }
                }
                else
                {
                    var name = formater.Format(new DataKey(_key, embedded.Name));

                    SetValue(name, _model);
                }
            }

            return this;
        }

        private DataFormat GetDataFormat()
        {
            if (_dataPacker != null)
            {
                return _dataPacker.DataFormat;
            }

            return _dataUnpacker.DataFormat;
        }

        private string GetRaw(string key)
        {
            if (_dataPacker != null)
            {
                return _dataPacker.GetRaw(key);
            }

            return _dataUnpacker.GetRaw(key);
        }

        /// <summary>
        ///     Creates a TModel object which contains the first row of data from the IDataUnpacker object. Returns null if there is no data.
        /// </summary>
        /// <returns>A TModel object containing the data from the IDataUnpacker object. Returns null if there is no data.</returns>
        public TModel FirstOrDefault()
        {
            return _isList ? _models.FirstOrDefault() : _model;
        }

        /// <summary>
        ///     Creates an <see cref="IList{TModel}"/> object which contains the data from the IDataUnpacker object.
        /// </summary>
        /// <returns>An <see cref="IList{TModel}"/> object containing the data from the IDataUnpacker object.</returns>
        public IList<TModel> ToList()
        {
            return _isList ? _models.ToList() : new List<TModel>() { _model };
        }
    }
}
